package com.demo.lock;

import sun.misc.Unsafe;

import java.lang.reflect.Field;
import java.util.concurrent.CountDownLatch;

/**
 * Created by hunter
 *
 * @Description 一个简单的自旋锁Demo, 当尝试获取锁, 获取到立即返回, 获取不到会等待锁释放, 等待过程中不释放占有的cpu
 *              具体应用类似 Atomic系列线程安全类
 */
public class SpinLock {

    // 要被update的数据
    private volatile int data;

    // 要被update的数据 比较值
    private volatile int data2;

    // 等待完成计数器
    private CountDownLatch dataLatch = new CountDownLatch(40);
    private CountDownLatch data2Latch = new CountDownLatch(40);

    // 要被unsafe拿来做对比的值
    private volatile int value = 0;

    // unsafe需要的偏移量
    private long valueOffset;

    // jdk api unsafe cas工具
    private Unsafe unsafe;

    {
        try {
            // 暴力获取unsafe
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);

            // 计算偏移量
            valueOffset = unsafe.objectFieldOffset(this.getClass().getDeclaredField("value"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 拿到锁, 即自旋, 更新成功之前都在循环之中, 占用cpu, 这里利用了CAS(缺点 : 存在ABA问题, 同时长时间获取不到浪费cpu)
     */
    private void getLock() {
        // 这里每次拿0与this.value对比, 如果相等, 将就this.value更新为1, 如果不相等, 将会返回false,
        // 0当做原来拿到的认为没被修改过的值, 具体看cas api和原理
        while (! unsafe.compareAndSwapInt(this, valueOffset, 0, 1)) {
            System.out.println(Thread.currentThread().getName() + " compareAndSwapInt failed, spinning ... ");

            // 这里可以sleep一下表示自旋中
        }
    }

    /**
     * 解锁, 这里将this.value更新为0即可, 因为如果是锁住的, this.value一直是 1, compareAndSwapInt一直在自旋
     * 其实这里应该有个本地变量标识占有锁的线程, 解锁时只有此线程才应该让他解锁
     */
    private void releaseLock() {
        this.value = 0;
    }

    /**
     * 加锁更新操作
     */
    public void update() {
        getLock();

        data++;

        releaseLock();
    }

    /**
     * 普通更新操作 用来做比较, 表明锁起到作用了
     */
    public void normalUpdate() {
        data2++;
    }

    public static void main(String[] args) throws InterruptedException {
        SpinLock lockTest = new SpinLock();

        for (int i = 0; i < 40; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    lockTest.update();
                }
                lockTest.dataLatch.countDown();
            }).start();
        }

        // 这里拿普通的++操作来对比, 可以看出是线程不安全的
        for (int i = 0; i < 40; i++) {
            new Thread(() -> {
                for (int j = 0; j < 1000; j++) {
                    lockTest.normalUpdate();
                }
                lockTest.data2Latch.countDown();
            }).start();
        }

        // 等待结束
        lockTest.dataLatch.await();
        lockTest.data2Latch.await();

        // 打印值检测线程安全结果
        System.out.println(lockTest.data);
        System.out.println(lockTest.data2);
    }
}
